#include "player.h"
#include <boost/bind.hpp>
#include <cassert>
#include <cmath>
#include <iostream>
#include "song.h"
#include "event.h"
#include "track.h"
#include "pattern.h"
#include "songitem.h"
#include "instrument.h"
using namespace std;
using namespace boost;

void *initThread(ALLEGRO_THREAD *pThread, void *pPlayer) {
     static_cast<Player*>(pPlayer)->run();
     return NULL;
}

Player::Player()
: m_pSong(NULL), m_repeat(true), m_songItemNum(0), m_mode(Player::PlayPattern),
  m_playing(false), m_quit(false), m_pThread(NULL), m_row(-1), m_startRow(0),
  m_requeue(false) {
}

Player::~Player() {
     if (!m_quit)
	  quit();
     if (m_pThread)
	  al_destroy_thread(m_pThread);
}

void Player::start() {
     m_pThread = al_create_thread(initThread, this);
     if (!m_pThread) {
	  cerr << "player: couldn't create thread. exiting" << endl;
	  exit(1);
     }
     al_start_thread(m_pThread);
}

// thread execution starts here
void Player::run() {
     updateTempo();
     doEvents();
}

// handle input events
void Player::doEvents() {
     scheduleEvents();
     while (!m_quit) {
	  int ret = m_midi.checkInputEvents();
	  // set startRow to 0 when looping so events are scheduled
	  // correctly relative to row 0
	  if (ret == Midi::Loop) {
	       m_startRow = 0;
	       if (m_mode == Player::PlaySong)
		    m_songItemNum = (m_songItemNum + 1) % (m_pSong->length());
	       scheduleEvents();
	  }
	  if (ret == Midi::IncRow) {
	       if (m_requeue) {
		    // clear queue from this point on
		    m_midi.clearQueue();
		    m_startRow = m_row + 1;
		    m_requeue = false;
		    scheduleEvents();
	       } else {
		    m_row = (m_row + 1) % (m_pSong->patternLength());
	       }
	  }
     }
}

void Player::setSong(Song *pSong) {
     m_pSong = pSong;
}

void Player::setMode(Player::Mode mode) {
     m_mode = mode;
}

void Player::setRepeat(bool repeat) {
     m_repeat = repeat;
}

void Player::setSongItemNum(int num) {
     m_songItemNum = num;
}

void Player::startPlaying() {
     if (!m_pSong || m_playing)
	  return;
     updateTempo();
     m_startRow = m_row + 1;
     m_playing = true;
     scheduleEvents();
}

void Player::stopPlaying() {
     if (!m_playing)
	  return;
     m_playing = false;
     m_midi.clearQueue();
     m_midi.allNotesOff();
}

void Player::togglePlaying() {
     if (m_playing)
	  stopPlaying();
     else
	  startPlaying();
}

void Player::quit() {
     stopPlaying();
     m_quit = true;
     // send midi event so player thread doesn't keep waiting for one
     m_midi.scheduleEcho(0);
     if (m_pThread)
	  al_join_thread(m_pThread, NULL);
}

void Player::updateTempo() {
     if (m_pSong)
	  m_midi.setTempo(m_pSong->bpm());
}

void Player::connectPatternChange() {

     // save the currently connected song item number
     static int songItemNum=-1;
     static vector<signals2::connection> connections;
     
     // disconnect from any previously connected patterns 
     // if current m_songItemNum changed     
     if (m_songItemNum != songItemNum) {
	  while (connections.size()) {
	       connections[connections.size()-1].disconnect();
	       connections.pop_back();
	  }
     }

     // connect to each of the playing pattern's onchange signal
     Pattern *pPat=NULL;
     Track *pTrack=NULL;
     for (int trackIndex=0; trackIndex<m_pSong->trackCount(); trackIndex++) {
	  pTrack = m_pSong->trackAt(trackIndex);
	  pPat = pTrack->songItemAt(m_songItemNum)->pattern();
	  if (!pPat)
	       continue;
	  Pattern::OnChange::slot_type slot = bind(&Player::patternChanged, this, _1, _2);
	  connections.push_back(pPat->onChange(slot));
     }
}

// playing patterns onChange slot
void Player::patternChanged(Pattern *pPat, int eventIndex) {
     // ignore if changed row is before playing one - it will be queued correctly
     if (eventIndex <= m_row)
	  return;
     // requeue when next note increment is received
     m_requeue = true;
}

void Player::scheduleEvents(int rowOffset) {
     if (!m_playing)
	  return;
     Track *pTrack = NULL;
     Pattern *pPat = NULL;
     Event *pEvent = NULL;
     int eventIndex;

     // find out when patterns are edited so we can reschedule events
     connectPatternChange();

     for (eventIndex=m_startRow; eventIndex<m_pSong->patternLength(); eventIndex++) {
	  // schedule control event increment player row for display
	  m_midi.scheduleEcho(rowTick(eventIndex - m_startRow), Midi::IncRow);
	  for (int trackIndex=0; trackIndex<m_pSong->trackCount(); trackIndex++) {
	       pTrack = m_pSong->trackAt(trackIndex);
	       assert(pTrack);
	       pPat = pTrack->songItemAt(m_songItemNum)->pattern();
	       if (!pPat)
		    continue;
	       pEvent = pPat->eventAt(eventIndex);
	       assert(pEvent);
	       scheduleEvent(pEvent, eventIndex, pTrack, rowOffset);
	  }
     }
     if (m_repeat)
	  scheduleLoopEvent(eventIndex, rowOffset);
}

int Player::rowTick(int row) {
     return Midi::ppq / m_pSong->rowsPerBeat() * row;
}

void Player::scheduleEvent(Event *pEvent, int row, Track *pTrack, int rowOffset) {
     // todo - effects
     Instrument *pInst = m_pSong->instrumentAt(pEvent->instNum());
     if (!pInst)
	  return;
     int tick = rowTick(row - m_startRow + rowOffset);
     int channel = pInst->channel();
     if (pEvent->type() == Event::Note) {
	  int note = pEvent->number();
	  int vel = pEvent->val();
	  if (note > 0) { // normal note
	       if (pTrack->oldNote() >= 0) // turn off old note if there is one
		    m_midi.scheduleNoteOff(channel, pTrack->oldNote(), tick);
	       m_midi.scheduleNoteOn(channel, note, vel, tick); // note on
	       pTrack->setOldNote(pEvent->number()); // store old note
	  } else if (note == Event::NoteOff) { // note off row
	       if (pTrack->oldNote() >= 0) // turn off old note if there is one
		    m_midi.scheduleNoteOff(channel, pTrack->oldNote(), tick);
	  }
     } else if (pEvent->type() == Event::ControlChange) {
	  m_midi.scheduleControlChange(channel, pEvent->number(), 
				       pEvent->val(), tick);
     }
}

void Player::scheduleLoopEvent(int row, int rowOffset) {
     int tick = rowTick(row - m_startRow + rowOffset);
     m_midi.scheduleEcho(tick);
}

int Player::row() {
     return m_row;
}

bool Player::playing() {
     return m_playing;
}

int Player::songItemNum() {
     return m_songItemNum;
}

void Player::setRow(int row) {
     m_row = row;
}

int Player::msToTicks(int ms) {
     const double sPerM = 60;
     const double msPerS = 1000;
     double msPerM = sPerM * msPerS;
     double ticksPerM = m_pSong->bpm() * Midi::ppq;
     double ticksPerMs = ticksPerM / msPerM;
     int ticks = ticksPerMs * (double)ms;
     return ticks;
}

// play a note immediately
void Player::playNote(int num, int inst) {
     // convert miliseconds to ticks
     // have to calc every time - depends on song bpm
     int ticks = msToTicks(Player::NoteLength);
     Instrument *pInst = m_pSong->instrumentAt(inst);
     // don't play if the instrument doesn't exist
     if (!pInst)
	  return;
     m_midi.playNote(num, Player::Velocity, ticks, pInst->channel());
}
